﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Collections;
using System.Threading;
using Microsoft.TeamFoundation.Client;
using System.Net;
using TeamFoundation.ContinuousMerge.Plugins;
using System.Xml.Linq;

namespace TeamFoundation.ContinuousMerge
{
    /// <summary>
    /// Represents the continuous merge runtime.
    /// </summary>
    public class Runtime : IServiceProvider
    {
        private RuntimeConfiguration _configuration;
        private BlockingCollection<Tasks.TaskPost> _tasks = new BlockingCollection<TeamFoundation.ContinuousMerge.Tasks.TaskPost>();
        private TeamFoundationServer _tfs;

        private volatile bool _running;
        private volatile bool _paused;
        private Thread _workerThread;
        private SemaphoreSlim _pauseSemaphore = new SemaphoreSlim(1, 1);

        private volatile Dictionary<Type, Plugin> _services = new Dictionary<Type, Plugin>();

        /// <summary>
        /// Creates a new instance of the <see cref="Runtime"/>
        /// class.
        /// </summary>
        /// <param name="configuration">The configuration data.</param>
        public Runtime(RuntimeConfiguration configuration)
        {
            _configuration = configuration;
        }

        #region Tasking
        /// <summary>
        /// Posts a task to be executed.
        /// </summary>
        /// <param name="post">The task to post.</param>
        public void PostTask(Tasks.TaskPost post)
        {
            _tasks.Add(post);
        }

        /// <summary>
        /// Executes the runtime.
        /// </summary>
        public void Run()
        {
            if (_running)
                return;
            _running = true;

            _workerThread = new Thread(Worker);
            _workerThread.Start();
        }

        /// <summary>
        /// Stops the runtime.
        /// </summary>
        public void Stop()
        {
            if (!_running)
                return;
            _running = false;
            _workerThread.Join();
        }

        /// <summary>
        /// Pauses the runtime.
        /// </summary>
        public void Pause()
        {
            if (_paused)
                return;
            _paused = true;
            _pauseSemaphore.Wait();
        }

        /// <summary>
        /// Continues the runtime.
        /// </summary>
        public void Continue()
        {
            if (!_paused)
                return;
            _paused = false;
            _pauseSemaphore.Release();
        }

        /// <summary>
        /// The worker thread.
        /// </summary>
        private void Worker()
        {
            while (_running)
            {
                bool semaphoreAquired = false;
                try
                {
                    if (semaphoreAquired = _pauseSemaphore.Wait(1000))
                    {
                        Tasks.TaskPost post;
                        if (_tasks.TryRemove(out post, 1000))
                        {
                            post.Execute(this);
                        }
                    }
                }
                finally
                {
                    if (semaphoreAquired)
                        _pauseSemaphore.Release();
                }
            }
        }
        #endregion

        /// <summary>
        /// Refreshes the configuration.
        /// </summary>
        internal void RefreshConfiguration()
        {
            _configuration = _configuration.Refresh();

            var oldTfs = _tfs;
            _tfs = CreateTeamFoundationServer();
            if (oldTfs != null)
            {
                oldTfs.Dispose();
            }

            var dispose = new List<Plugin>();
            var newServices = LoadServices();
            foreach (var kvp in _services)
            {
                if (newServices.ContainsKey(kvp.Key))
                    newServices[kvp.Key] = kvp.Value;
                else
                    dispose.Add(kvp.Value);
            }
            
            foreach (var plug in newServices.Values)
                plug.Initialize(this);

            var oldServices = _services;
            _services = newServices;
            oldServices.Clear();
            foreach (var dis in dispose)
                dis.Dispose();
        }

        /// <summary>
        /// Loads the plugins.
        /// </summary>
        private Dictionary<Type, Plugin> LoadServices()
        {
            var plugins = new Dictionary<Type, Plugin>();

            using (var db = _configuration.GetDatabase())
            {
                foreach (var plug in db.Plugins)
                {
                    Type t = Type.GetType(plug.TypeName);
                    if (t != null)
                    {
                        Plugin newPlug = Activator.CreateInstance(t) as Plugin;
                        if (newPlug != null)
                        {
                            if (!string.IsNullOrEmpty(plug.Configuration))
                                newPlug.Configuration = XDocument.Parse(plug.Configuration);
                            plugins.Add(t, newPlug);
                        }
                    }
                }
            }

            return plugins;
        }

        /// <summary>
        /// Creates a team foundation server.
        /// </summary>
        private TeamFoundationServer CreateTeamFoundationServer()
        {
            TeamFoundationServer svr;
            string tfUser = _configuration["TFUser"];
            string tfPass = _configuration["TFPassword"];
            if (!string.IsNullOrEmpty(tfUser) && !string.IsNullOrEmpty(tfPass))
            {
                string tfDomain = null;
                int index = tfUser.IndexOf('\\');
                if (index >= 0)
                {
                    tfDomain = tfUser.Substring(0, index);
                    tfUser = tfUser.Substring(index + 1);
                }
                if (tfDomain == null)
                {
                    svr = new TeamFoundationServer(_configuration["TFServer"], new NetworkCredential(tfUser, tfPass));
                }
                else
                {
                    svr = new TeamFoundationServer(_configuration["TFServer"], new NetworkCredential(tfUser, tfPass, tfDomain));
                }
            }
            else
            {
                svr = new TeamFoundationServer(_configuration["TFServer"]);
            }
            svr.EnsureAuthenticated();
            return svr;
        }

        #region IServiceProvider Members
        /// <summary>
        /// Gets an implementation of a service from the runtime.
        /// </summary>
        /// <param name="serviceType">The type of service to get.</param>
        /// <returns>An implementation of the service, or null if it could not be found.</returns>
        public object GetService(Type serviceType)
        {
            var services = _services;
            Plugin result;
            if (services.TryGetValue(serviceType, out result))
                return result;
            else
                return _tfs.GetService(serviceType);
        }

        #endregion
    }
}
